package cn.zl.ZLM;

import cn.hutool.core.util.StrUtil;
import cn.zl.ZLM.core.ZLMApi;
import cn.zl.ZLM.event.callback.CallBackEventsHander;
import cn.zl.ZLM.helper.ZLMApiHelper;
import cn.zl.ZLM.structure.MK_EVENTS;
import cn.zl.ZLM.structure.MK_INI;
import cn.yunbamboos.Const;
import cn.zl.common.OrderConst;
import cn.zl.common.ZLConst;
import cn.zl.config.ZLConfig;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import jakarta.annotation.PreDestroy;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.io.File;
import java.util.HashSet;
import java.util.Set;

@Component
@Order(OrderConst.ORDER_ZLM_LAYER)
public class ZLMLayer implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(ZLMLayer.class);

    private static final String USER_DIR = StrUtil.format("{}{}data{}ZLM{}", System.getProperty("user.dir"), File.separator, File.separator, File.separator);

    @Resource
    private ZLConfig config;
    @Resource
    private CallBackEventsHander callBackEventsHander;

    @Override
    public void run(String... args) throws Exception {
        ZLMApi ZLM_API = Native.load("mk_api", ZLMApi.class);
        ZLMApiHelper.getInstance().setApi(ZLM_API);
        this.initINI(ZLM_API);
        this.initEvents(ZLM_API);
        this.startServer(ZLM_API);
        logger.info("[ZLM] 初始化ZLM程序成功");
    }

    /**
     * 初始化服务器配置
     */
    public void initINI(ZLMApi ZLM_API) {
        String rootPath = USER_DIR + "www";
        // 文件日志保存路径
        String log_file_path = USER_DIR + "log" + File.separator;
        // 文件日志保存天数
        int log_file_days = 5;

        logger.info("[ZLM] IP：{}", config.getZLMIp());
        logger.info("[ZLM] DIR：{}", USER_DIR);
        logger.info("[ZLM] rootPath：{}", rootPath);
        logger.info("[ZLM] LOG：{}", log_file_path);
        logger.info("[ZLM] LOG_FILE_DAYS：{}", log_file_days);
        logger.info("[ZLM] java.library.path：{}", System.getProperty("java.library.path"));

        // 初始化环境配置
        MK_INI ZLM_INI = ZLM_API.mk_ini_default();

        ZLM_API.mk_set_log(5, 15);

        // #服务器唯一id，用于触发hook时区别是哪台服务器
        ZLM_API.mk_ini_set_option(ZLM_INI, "general.mediaServerId", "JMediaServer");
        ZLM_API.mk_ini_set_option(ZLM_INI, "http.notFound", "<html><head><title>404 Not Found</title></head><body bgcolor=\"white\"><center><h1>您访问的资源不存在！</h1></center><hr><center>ZLMediaKit-4.0</center></body></html>");
        // #无人观看时，是否直接关闭(而不是通过on_none_reader hook返回close)
        // #此配置置1时，此流如果无人观看，将不触发on_none_reader hook回调，
        // #而是将直接关闭流
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.auto_close", 0);
        // #某个流无人观看时，触发hook.on_stream_none_reader事件的最大等待时间，单位毫秒
        // #在配合hook.on_stream_none_reader事件时，可以做到无人观看自动停止拉流或停止接收推流
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "general.streamNoneReaderDelayMS", 30000);
        // #播放最多等待时间，单位毫秒
        // #播放在播放某个流时，如果该流不存在，
        // #ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒
        // #如果在这个时间内，该流注册成功，那么会立即返回播放器播放成功
        // #否则返回播放器未找到该流，该机制的目的是可以先播放再推流
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "general.maxStreamWaitMS", 15000);
        // #是否开启转换为http-ts/ws-ts
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.enable_ts", 0);
        // #是否开启转换为hls(mpegts)
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.enable_hls", 1);
        // #是否开启转换为hls(fmp4)
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.enable_fmp4", 0);
        // #是否开启转换为rtsp/webrtc
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.enable_rtsp", 1);
        // #是否开启转换为rtmp/flv
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.enable_rtmp", 1);
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.enable_mp4", 0);
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.enable_hls_fmp4", 0);
        // #转协议是否开启音频
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.enable_audio", 0);
        // #是否将mp4录制当做观看者
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.mp4_as_player", 0);
        // #mp4切片大小，单位秒
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.mp4_max_second", 3600);
        // #http文件服务器根目录
        // #可以为相对(相对于本可执行程序目录)或绝对路径
        ZLM_API.mk_ini_set_option(ZLM_INI, "http.rootPath", rootPath);
        ZLM_API.mk_ini_set_option(ZLM_INI, "protocol.mp4_save_path", rootPath);
        ZLM_API.mk_ini_set_option(ZLM_INI, "protocol.hls_save_path", rootPath);
        // ###### 以下是按需转协议的开关，在测试ZLMediaKit的接收推流性能时，请把下面开关置1
        // ###### 如果某种协议你用不到，你可以把以下开关置1以便节省资源(但是还是可以播放，只是第一个播放者体验稍微差点)，
        // ###### 如果某种协议你想获取最好的用户体验，请置0(第一个播放者可以秒开，且不花屏)
        // #hls协议是否按需生成，如果hls.segNum配置为0(意味着hls录制)，那么hls将一直生成(不管此开关)
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.hls_demand", 0);
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.rtsp_demand", 0);
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.rtmp_demand", 0);
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.ts_demand", 0);
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "protocol.fmp4_demand", 0);
        // #是否广播 hls切片(ts/fmp4)完成通知(on_record_ts)
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "hls.broadcastRecordTs", 1);
        // #m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个)
        // #如果设置为0，则不删除切片，而是保存为点播
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "hls.segNum", 3);
        // #hls最大切片时间
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "hls.segDur", 2);

        // #允许访问http api和http文件索引的ip地址范围白名单，置空情况下不做限制
        // allow_ip_range=::1,127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255
        Set<String> ips = new HashSet<>();
        ips.add("localhost");
        ips.add("127.0.0.1");
        ips.add(config.getIp());
        ips.add(config.getSIPIP());
        ips.add(config.getZLMIp());
        String allow_ip_range = StrUtil.join(",", ips);
        logger.info("allow_ip_range: {}", allow_ip_range);
        ZLM_API.mk_ini_set_option(ZLM_INI, "http.allow_ip_range", allow_ip_range);

        // rtp_proxy 相关
        // #随机端口范围，最少确保36个端口
        // #该范围同时限制rtsp服务器udp端口范围
        ZLM_API.mk_ini_set_option(ZLM_INI, "rtp_proxy.port_range", "30000-40000");


        ZLM_API.mk_ini_set_option(ZLM_INI, "rtp_proxy.port_range", "30000-40000");

        // #mp4录制或mp4点播的应用名，通过限制应用名，可以防止随意点播
        // #点播的文件必须放置在此文件夹下
        ZLM_API.mk_ini_set_option(ZLM_INI, "record.appName", "record");
        // #mp4录制写文件缓存，单位BYTE,调整参数可以提高文件io性能
        ZLM_API.mk_ini_set_option(ZLM_INI, "record.fileBufSize", "65536");
        // #mp4点播每次流化数据量，单位毫秒，
        // #减少该值可以让点播数据发送量更平滑，增大该值则更节省cpu资源
        ZLM_API.mk_ini_set_option(ZLM_INI, "record.sampleMS", "500");
        // #mp4录制完成后是否进行二次关键帧索引写入头部
        ZLM_API.mk_ini_set_option(ZLM_INI, "record.fastStart", "1");
        // #MP4点播(rtsp/rtmp/http-flv/ws-flv)是否循环播放文件
        ZLM_API.mk_ini_set_option_int(ZLM_INI, "record.fileRepeat", 1);
        // #MP4录制写文件格式是否采用fmp4，启用的话，断电未完成录制的文件也能正常打开
        ZLM_API.mk_ini_set_option(ZLM_INI, "record.enableFmp4", "1");

        //导出配置到文件
        String conf = StrUtil.format("{}config{}conf.ini", USER_DIR, File.separator);
        logger.info("[ZLM] CONF：{}", conf);
        ZLM_API.mk_ini_dump_file(ZLM_INI, conf);
        Pointer pointer = ZLM_API.mk_ini_dump_string(ZLM_INI);
        //初始化zmk服务器
        ZLM_API.mk_env_init2(config.getZLM().getThreadNum(), config.getZLM().getLogLevel(), 2, log_file_path, log_file_days, 0, pointer, 0, null, null);
        //需要手动释放SDK资源
        ZLM_API.mk_free(pointer );
    }

    /**
     * 初始化事件回调
     */
    public void initEvents(ZLMApi ZLM_API) {
        //全局回调
        MK_EVENTS ZLM_EVENT = callBackEventsHander.get();
        ZLM_API.mk_events_listen(ZLM_EVENT);
    }

    /**
     * 启动流媒体服务器
     */
    public void startServer(ZLMApi ZLM_API) {
        boolean result = false;
        //创建http服务器 0:失败,非0:端口号
        short http_server_port = ZLM_API.mk_http_server_start((short) config.getZLMPort(ZLConst.PLAY_URL_SCHEME.HTTP_FLV), 0);
        result = http_server_port == 0;
        logger.info("[ZLM] HTTP流媒体服务启动：{} IP: {} 端口：{}", result ? "失败" : "成功", config.getZLMIp(), http_server_port);

        //创建rtsp服务器 0:失败,非0:端口号
        short rtsp_server_port = ZLM_API.mk_rtsp_server_start((short) config.getZLMPort(ZLConst.PLAY_URL_SCHEME.RTSP), 0);
        result = rtsp_server_port == 0;
        logger.info("[ZLM] RTSP流媒体服务启动：{} IP: {} 端口：{}", result ? "失败" : "成功", config.getZLMIp(), rtsp_server_port);

        //创建rtmp服务器 0:失败,非0:端口号
        short rtmp_server_port = ZLM_API.mk_rtmp_server_start((short) config.getZLMPort(ZLConst.PLAY_URL_SCHEME.RTMP), 0);
        result = rtmp_server_port == 0;
        logger.info("[ZLM] RTMP流媒体服务启动：{} IP: {} 端口：{}", result ? "失败" : "成功", config.getZLMIp(), rtmp_server_port);
    }

    /**
     * 关闭流媒体服务器
     */
    public void stopServer() {
        ZLMApi ZLM_API = ZLMApiHelper.getInstance().getApi();
        if (ZLM_API != null) {
            ZLM_API.mk_free(null);
            ZLM_API.mk_stop_all_server();
            logger.info("[ZLM] 关闭所有流媒体服务");
            try {
                Thread.sleep(500);
            } catch (Exception e) {
                logger.error("[ZLM] stop error", e);
            }
        }
    }

    /**
     * 释放资源
     */
    @PreDestroy
    public void release() {
        this.stopServer();
    }

    private boolean isWindow() {
        return System.getProperty("os.name").toLowerCase().contains("win");
    }

    private static boolean isLinux() {
        return System.getProperty("os.name").toLowerCase().contains("linux");
    }

}